package com.kmxd.ams.app.search;

import static com.kmxd.ams.core.common.constant.Constants.*;
import static java.util.Arrays.stream;

import cn.easyes.annotation.rely.FieldType;
import cn.easyes.annotation.rely.HighLightTypeEnum;
import cn.easyes.common.constants.BaseEsConstants;
import cn.easyes.common.utils.CollectionUtils;
import cn.easyes.core.biz.EntityInfo;
import cn.easyes.core.biz.EsPageInfo;
import cn.easyes.core.conditions.select.LambdaEsQueryWrapper;
import cn.easyes.core.toolkit.EntityInfoHelper;
import cn.easyes.core.toolkit.FieldUtils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.*;
import com.kmxd.ams.app.arrange.command.CommonQueryCmd;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.app.search.command.CommonAuthCmd;
import com.kmxd.ams.app.search.command.ConditionCmd;
import com.kmxd.ams.app.search.command.SearchAuthCmd;
import com.kmxd.ams.app.search.command.StatisticsCmd;
import com.kmxd.ams.client.archive.*;
import com.kmxd.ams.client.archive.dto.ArchiveRelationArchQueryDTO;
import com.kmxd.ams.client.archive.dto.ArchiveTypeQueryDTO;
import com.kmxd.ams.client.archive.vo.*;
import com.kmxd.ams.client.arrange.ICatalogSvc;
import com.kmxd.ams.client.arrange.dto.CatalogTreeQueryDTO;
import com.kmxd.ams.client.es.*;
import com.kmxd.ams.client.event.statistices.StatisticsUsingOpEvent;
import com.kmxd.ams.client.jy.IAuthDetailsSvc;
import com.kmxd.ams.client.oss.IEfileRecordSvc;
import com.kmxd.ams.client.oss.dto.EfileRecordQueryDTO;
import com.kmxd.ams.client.oss.vo.EfileRecordListVO;
import com.kmxd.ams.client.oss.vo.EfileRecordVO;
import com.kmxd.ams.client.search.ISearchSvc;
import com.kmxd.ams.client.search.dto.*;
import com.kmxd.ams.client.search.vo.*;
import com.kmxd.ams.client.system.ISysCropSvc;
import com.kmxd.ams.client.system.ISysFondsSvc;
import com.kmxd.ams.client.system.vo.SysFondsListVO;
import com.kmxd.ams.client.use.*;
import com.kmxd.ams.client.use.dto.UseBorrowCarQueryDTO;
import com.kmxd.ams.client.use.dto.UseFavoritesQueryDTO;
import com.kmxd.ams.client.use.vo.*;
import com.kmxd.ams.client.watermark.IWatermarkSvc;
import com.kmxd.ams.core.common.bo.ConditionBO;
import com.kmxd.ams.core.common.bo.OptionsModel;
import com.kmxd.ams.core.common.constant.*;
import com.kmxd.ams.core.common.enums.*;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.oss.bo.OssFile;
import com.kmxd.ams.core.oss.template.OssTemplate;
import com.kmxd.ams.core.util.*;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.SystemBO;
import com.kmxd.ams.infra.jy.entity.AuthDetails;
import com.kmxd.ams.infra.search.entity.*;
import com.kmxd.ams.infra.system.entity.SysFonds;
import com.kmxd.ams.infra.tmpl.dto.ArchTmplBO;
import com.kmxd.ams.infra.utils.EfileTableNameUtil;
import com.kmxd.ams.infra.utils.GroupUtil;
import java.io.File;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

/**
 * 检索es数据
 *
 * @author nickbi
 */
@Slf4j
@Service(SEARCH_SVC_PREFIX + SearchTypeConst.ES)
@AllArgsConstructor
public class SearchEsSvc implements ISearchSvc {

  /** 检索关键词长度 */
  public static final int SEARCH_WORDS_LENGTH = 40;

  private final IArchiveTypeSvc archiveTypeSvc;
  private final IDocumentEsSvc documentEsSvc;
  private final IVolumeEsSvc volumeEsSvc;
  private final IBoxEsSvc boxEsSvc;
  private final IProjectEsSvc projectEsSvc;
  private final ISysFondsSvc sysFondsSvc;
  private final ISysCropSvc sysCropSvc;
  private final ICatalogSvc catalogSvc;
  private final IEfileRecordSvc efileRecordSvc;
  private final IArchiveRelationSvc archiveRelationSvc;
  private final RestHighLevelClient restHighLevelClient;
  private final IArchiveAuthSvc authSvc;
  private final IUseBorrowCarSvc useBorrowCarSvc;
  private final IUseFavoritesSvc useFavoritesSvc;
  private final IArchiveFieldSvc archiveFieldSvc;
  private final OssTemplate ossTemplate;
  private final AmsEventPublisher amsEventPublisher;
  private final IWatermarkSvc watermarkSvc;
  private final IArchiveRuleCatalogSvc archiveRuleCatalogSvc;
  private final CommonAuthCmd authCmd;
  private final ConditionCmd conditionCmd;

  private final SearchAuthCmd searchAuthCmd;
  private final EfileTableNameUtil tableNameUtil;
  private final StatisticsCmd statisticsCmd;
  private final Map<String, String> highlightFieldMap =
      ImmutableMap.of(
          "year",
          "yearHl",
          "department",
          "departmentHl",
          "archCode",
          "archCodeHl",
          "fileType",
          "fileTypeHl",
          "title",
          "titleHl",
          "titleKw",
          "titleHl",
          "archCodeKw",
          "archCodeHl");

  // 二次过滤默认字段
  private final ImmutableSet<String> filterFieldSet =
      ImmutableSet.of(
          "year", "department", "archCodeKw", "titleKw", "docNumber", "author", "fondsName");

  public static List<Object> buildKey(DocumentEs source) {
    Map<String, Object> documentMap = BeanUtil.beanToMap(source);
    ArrayList<Object> objects = Lists.newArrayList();
    objects.add(documentMap.get(ArchConst.ARCH_TYPE_ID));
    objects.add(documentMap.get(ArchConst.TABLE_ID));
    return objects;
  }

  @Override
  public QuickSearchVO quickSearch(QuickSearchDTO req) {
    Page reqPage = new Page<>(req.getPage(), req.getSize());
    if (StrUtil.isBlank(req.getKeyword())) {
      throw BizTipException.instance(ErrorCode.SEARCH_KEYWORDS_IS_NULL, "检索关键词不能为空");
    }
    if (StrUtil.length(req.getKeyword()) > SEARCH_WORDS_LENGTH) {
      throw BizTipException.instance(ErrorCode.SEARCH_KEYWORDS_TOO_LONG, "检索关键词过长");
    }

    LambdaEsQueryWrapper<DocumentEs> wrapper = buildQueryWrapper(req, true);
    buildAuth(wrapper);
    EsPageInfo<DocumentEs> page = new EsPageInfo<>();
    // 原文检索，添加inner_hits,添加highLight
    // 原文检索
    if (ObjectUtil.equals(QuickSearchEnum.ORIGINAL, req.getType())) {
      //      buildOriginQuery(req, reqPage, wrapper);
      queryEs(wrapper, page, true);
    } else {
      // 智能分词
      if (BooleanUtil.isTrue(req.getParticiple())) {
        page =
            documentEsSvc.page(
                wrapper, Math.toIntExact(reqPage.getCurrent()), Math.toIntExact(reqPage.getSize()));
      } else {
        // 模糊查询
        buildCatalogQuery(reqPage, wrapper, req);
        queryEs(wrapper, page, false);
      }
    }

    List<Long> archTypes = searchAuthCmd.getArchTypeByUser(SecurityUtil.getUserId());

    // 查询单位
    List<OptionsModel<String>> orgList = Lists.newArrayList();
    List<OptionsModel<String>> yearList =
        getOptionList(req, archTypes, ArchConst.FIELD_YEAR, ArchConst.FIELD_YEAR);
    List<OptionsModel<String>> deptList = Lists.newArrayList();
    List<OptionsModel<String>> archiveTypeList =
        getOptionList(req, archTypes, ArchConst.ARCH_TYPE_ID, ArchConst.ARCH_TYPE_NAME);
    List<DocumentEs> list = buildHighlightAndFlag(page.getList(), DocumentEs.class);

    long userId = SecurityUtil.getUserId();
    CompletableFuture.runAsync(
        () -> {
          statisticsCmd.runSearch(userId, req, list);
        });

    return QuickSearchVO.builder()
        .fondsList(orgList)
        .yearList(yearList)
        .deptList(deptList)
        .archTypeList(archiveTypeList)
        .list(list)
        .total(page.getTotal())
        .build();
  }

  /**
   * 手动拼接，请求es数据
   *
   * @param wrapper
   * @param page
   */
  private void queryEs(
      LambdaEsQueryWrapper<DocumentEs> wrapper, EsPageInfo<DocumentEs> page, boolean isOriginQry) {
    SearchResponse searchResponse = documentEsSvc.search(wrapper);
    Optional<SearchHit[]> searchHits =
        Optional.ofNullable(searchResponse).map(SearchResponse::getHits).map(SearchHits::getHits);
    if (searchHits.isPresent()) {
      List<DocumentEs> list =
          stream(searchHits.get())
              .map(item -> isOriginQry ? parseOriginOne(item) : parseOne(item))
              .collect(Collectors.toList());
      page.setList(list);
      AtomicLong repeatNum = new AtomicLong(0);
      Optional.ofNullable(searchResponse.getHits())
          .flatMap(item -> Optional.ofNullable(item.getTotalHits()))
          .ifPresent(totalHits -> repeatNum.getAndAdd(totalHits.value));
      long total = repeatNum.get();
      page.setTotal(total);
    }
  }

  /**
   * 条目模糊查询，高亮字段要自主设置，拼接查询条件
   *
   * @param reqPage
   * @param wrapper
   */
  private void buildCatalogQuery(
      Page reqPage, LambdaEsQueryWrapper<DocumentEs> wrapper, QuickSearchDTO req) {
    try {
      BoolQueryBuilder builder = getSourceQuery(documentEsSvc.getBaseMapper().getSource(wrapper));
      HighlightBuilder highlightBuilder =
          new HighlightBuilder()
              .noMatchSize(0)
              .numOfFragments(2)
              .order(HighlightBuilder.Order.SCORE)
              .preTags(ES_HIGHLIGHT_PRE_TAG)
              .postTags(ES_HIGHLIGHT_POST_TAG)
              .requireFieldMatch(true);
      highlightBuilder.highlighterType(HighLightTypeEnum.UNIFIED.getValue());
      // 模糊查询，单独设置高亮查询信息，优化字段高亮
      if (!BooleanUtil.isTrue(req.getParticiple())) {
        BoolQueryBuilder hlQueryBuilder = QueryBuilders.boolQuery();
        List<String> keywordsList = StrUtil.split(req.getKeyword(), " ");
        keywordsList.forEach(
            keyword -> {
              hlQueryBuilder
                  .should(
                      QueryBuilders.matchQuery(FieldUtils.val(DocumentEs::getArchCode), keyword))
                  .should(QueryBuilders.matchQuery(FieldUtils.val(DocumentEs::getTitle), keyword));
            });

        highlightBuilder.highlightQuery(hlQueryBuilder);
      }

      highlightFieldMap.forEach((field, fieldHl) -> highlightBuilder.field(field));
      // 特殊设置title.keyword,避免数据匹配了 数据未高亮问题
      //      highlightBuilder.field("title.keyword");
      // 模糊查询，重新设置高亮查询条件
      String[] exclude = {"records"};
      String[] include = {};
      SearchSourceBuilder query =
          new SearchSourceBuilder().query(builder).fetchSource(include, exclude);
      query.highlighter(highlightBuilder);
      query.trackTotalHits(true);

      ImmutablePair<Integer, Integer> pair =
          buildPageInfo(Math.toIntExact(reqPage.getCurrent()), Math.toIntExact(reqPage.getSize()));
      query.from(pair.getLeft());
      query.size(pair.getRight());
      wrapper.setSearchSourceBuilder(query);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private DocumentEs parseOriginOne(SearchHit searchHit) {
    EntityInfo entityInfo = EntityInfoHelper.getEntityInfo(documentEsSvc.getEntityClass());
    // 解析json
    DocumentEs entity =
        JSON.parseObject(
            searchHit.getSourceAsString(),
            documentEsSvc.getEntityClass(),
            entityInfo.getExtraProcessor());
    entity.setId(Long.valueOf(searchHit.getId()));
    // 高亮字段处理
    if (CollectionUtils.isNotEmpty(entityInfo.getHighLightParams())) {
      String valRecords = FieldUtils.val(DocumentEs::getRecords);
      String valOcrContent =
          FieldUtils.val(DocumentEs::getRecords)
              + "."
              + FieldUtils.val(EFileRecordEs::getOcrContent);
      String valOcrTitle =
          FieldUtils.val(DocumentEs::getRecords) + "." + FieldUtils.val(EFileRecordEs::getTitle);
      Map<String, SearchHits> innerHits = searchHit.getInnerHits();
      if (Objects.isNull(innerHits) || !innerHits.containsKey(valRecords)) {
        return entity;
      }
      SearchHit[] records = innerHits.get(valRecords).getHits();
      List<EFileRecordEs> list =
          stream(records)
              .map(
                  record -> {
                    EFileRecordEs eFileRecordEs =
                        JSONUtil.toBean(record.getSourceAsString(), EFileRecordEs.class);
                    Map<String, HighlightField> highlightFields = record.getHighlightFields();
                    if (highlightFields.containsKey(valOcrContent)) {
                      HighlightField highlightField = highlightFields.get(valOcrContent);
                      String highLightValue =
                          stream(highlightField.getFragments())
                              .findFirst()
                              .map(Text::string)
                              .orElse(BaseEsConstants.EMPTY_STR);
                      eFileRecordEs.setOcrContent(highLightValue);
                    }
                    if (highlightFields.containsKey(valOcrTitle)) {
                      HighlightField highlightField = highlightFields.get(valOcrTitle);
                      String highLightValue =
                          stream(highlightField.getFragments())
                              .findFirst()
                              .map(Text::string)
                              .orElse(BaseEsConstants.EMPTY_STR);
                      eFileRecordEs.setTitle(highLightValue);
                    }

                    return eFileRecordEs;
                  })
              .collect(Collectors.toList());
      entity.setRecords(list);
    }
    return entity;
  }

  /**
   * 条目 模糊查询，高亮字段解析
   *
   * @param searchHit
   * @return
   */
  private DocumentEs parseOne(SearchHit searchHit) {
    EntityInfo entityInfo = EntityInfoHelper.getEntityInfo(documentEsSvc.getEntityClass());
    // 解析json
    DocumentEs entity =
        JSON.parseObject(
            searchHit.getSourceAsString(),
            documentEsSvc.getEntityClass(),
            entityInfo.getExtraProcessor());
    entity.setId(Long.valueOf(searchHit.getId()));
    // 高亮字段处理

    if (CollectionUtils.isNotEmpty(highlightFieldMap)) {
      Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
      if (Objects.isNull(highlightFields)) {
        return entity;
      }
      JSONObject jsonObj = JSONUtil.parseObj(entity);
      highlightFieldMap.forEach(
          (field, fieldHl) -> {
            HighlightField highlightField = highlightFields.get(field);
            if (ObjectUtil.isEmpty(highlightField)
                || ObjectUtil.isEmpty(highlightField.getFragments())) {
              return;
            }
            Text[] fragments = highlightField.getFragments();
            String value = stream(fragments).map(Text::toString).collect(Collectors.joining());
            jsonObj.set(fieldHl, value);
          });
      // 特殊设置title.keyword,避免数据匹配了 数据未高亮问题
      setTitleKeywordHl(highlightFields, jsonObj);
      entity = JSONUtil.toBean(jsonObj, DocumentEs.class);
    }
    return entity;
  }

  /**
   * 部分查询条件title查询高亮会有问题，必须指定title.keword才行
   *
   * <p>这个方法是提升标题高亮的
   *
   * @param highlightFields
   * @param jsonObj
   */
  private static void setTitleKeywordHl(
      Map<String, HighlightField> highlightFields, JSONObject jsonObj) {
    HighlightField titleKWHighlight = highlightFields.get("title.keyword");
    if (ObjectUtil.isEmpty(titleKWHighlight)) {
      return;
    }
    String titleKWHl =
        stream(titleKWHighlight.getFragments()).map(Text::toString).collect(Collectors.joining());
    HighlightField titleHighlight = highlightFields.get("title");
    if (ObjectUtil.isNull(titleHighlight) || ObjectUtil.isEmpty(titleHighlight.getFragments())) {
      jsonObj.set("titleHl", titleKWHl);
      return;
    }
    String titleHl =
        stream(titleHighlight.getFragments()).map(Text::toString).collect(Collectors.joining());
    if (titleHl.contains(ES_HIGHLIGHT_PRE_TAG)) {
      jsonObj.set("titleHl", titleHl);
    } else {
      jsonObj.set("titleHl", titleKWHl);
    }
  }

  /**
   * 修复高亮为空问题
   *
   * @param list
   */
  private <T> List<T> buildHighlightAndFlag(List<? extends SystemBO> list, Class<T> clazz) {
    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }
    Map<List<Object>, ? extends List<? extends SystemBO>> groupMap =
        list.stream()
            .collect(
                Collectors.groupingBy(
                    item ->
                        GroupUtil.buildKeyByField(
                            item, ImmutableList.of(ArchConst.ARCH_TYPE_ID, ArchConst.TABLE_ID))));
    List<UseFavoritesListVO> useFavoritesList = Lists.newArrayList();
    List<UseBorrowCarListVO> useBorrowCarList = Lists.newArrayList();

    groupMap.forEach(
        (group, groupList) -> {
          if (ObjectUtil.isEmpty(group) || ObjectUtil.isEmpty(groupList)) {
            return;
          }
          // 设置收藏标识
          List<Long> archIds = groupList.stream().map(SystemBO::getId).collect(Collectors.toList());
          List<UseFavoritesListVO> useFavoritesListVOS =
              useFavoritesSvc.selectListByIds((Long) group.get(0), (String) group.get(1), archIds);
          if (ObjectUtil.isNotEmpty(useFavoritesListVOS)) {
            useFavoritesList.addAll(useFavoritesListVOS);
          }
          // 设置借阅车标识
          List<UseBorrowCarListVO> useBorrowCarListVOS =
              useBorrowCarSvc.selectListByIds((Long) group.get(0), (String) group.get(1), archIds);
          if (ObjectUtil.isNotEmpty(useBorrowCarListVOS)) {
            useBorrowCarList.addAll(useBorrowCarListVOS);
          }
        });
    // todo... 按门类id，tableId分组，进行收藏、借阅车信息设置，一次请求两组数据，不单独进行请求
    List<Object> collect =
        list.stream()
            .map(
                doc -> {
                  JSONObject parseObj = JSONUtil.parseObj(doc);
                  highlightFieldMap.forEach(
                      (key, value) -> {
                        if (StrUtil.isBlank(parseObj.getStr(value))) {
                          parseObj.set(value, parseObj.getStr(key));
                        }
                      });

                  // 设置收藏标识
                  setFavoritesFlag(parseObj);
                  // 设置借阅车
                  setBorrowCarFlag(parseObj);
                  parseObj.set(
                      "auth",
                      authCmd.getAuth(
                          ConvertUtil.getValue(doc, ArchConst.ARCH_TYPE_ID, Long.class),
                          (SystemBO) doc));
                  return JSONUtil.toBean(parseObj, clazz);
                })
            .collect(Collectors.toList());
    return (List<T>) collect;
  }

  private void setBorrowCarFlag(JSONObject parseObj) {
    Boolean favoritesFlag =
        getBorrowCarFlag(
            parseObj.getLong(ArchConst.ARCH_TYPE_ID),
            parseObj.getStr(ArchConst.TABLE_ID),
            parseObj.getLong(ArchConst.DEF_ID));
    parseObj.set(ArchConst.ARCH_BORROW_CAR_FLAG, favoritesFlag);
  }

  private Boolean getBorrowCarFlag(Long archTyeId, String tableId, Long archId) {
    UseBorrowCarQueryDTO qryFavorites = new UseBorrowCarQueryDTO();
    qryFavorites.setTableId(tableId);
    qryFavorites.setArchTypeId(archTyeId);
    qryFavorites.setArchId(archId);
    UseBorrowCarVO borrowCar = useBorrowCarSvc.getBorrowCar(qryFavorites);
    return ObjectUtil.isNotNull(borrowCar);
  }

  private Boolean getFavoritesFlag(Long archTyeId, String tableId, Long archId) {
    // 设置收藏标识
    UseFavoritesQueryDTO qryFavorites = new UseFavoritesQueryDTO();
    qryFavorites.setTableId(tableId);
    qryFavorites.setArchTypeId(archTyeId);
    qryFavorites.setArchId(archId);
    UseFavoritesVO favorites = useFavoritesSvc.getFavorites(qryFavorites);
    return ObjectUtil.isNotNull(favorites);
  }

  private void setFavoritesFlag(JSONObject parseObj) {
    // 设置收藏标识
    Boolean favoritesFlag =
        getFavoritesFlag(
            parseObj.getLong(ArchConst.ARCH_TYPE_ID),
            parseObj.getStr(ArchConst.TABLE_ID),
            parseObj.getLong(ArchConst.DEF_ID));
    parseObj.set(ArchConst.ARCH_FAVORITES_FLAG, favoritesFlag);
  }

  @Override
  public AdvancedSearchVO advancedSearch(AdvancedSearchDTO req) {
    Page reqPage = new Page<>(req.getPage(), req.getSize());
    String tableId = req.getTableId();
    if (tableId.contains(ArchTmplBO.DOCUMENT)) {
      return byCondition(reqPage, documentEsSvc, req);
    } else if (tableId.contains(ArchTmplBO.VOLUME)) {
      return byCondition(reqPage, volumeEsSvc, req);
    }
    throw BizTipException.instance(ErrorCode.NOT_FOUND, "未知档案类型");
  }

  /**
   * 通过条件查询
   *
   * @param reqPage 分页
   * @param serviceEs 对应的es service
   * @param req 请求参数
   * @param <T> 结果类型
   * @return
   */
  public <T> AdvancedSearchVO byCondition(
      Page reqPage, IServiceEs serviceEs, AdvancedSearchDTO req) {
    try {
      LambdaEsQueryWrapper<T> wrapper = new LambdaEsQueryWrapper<>();
      Request request = new Request("POST", "/_sql/translate");
      String sql = QueryUtil.buildEsSql(req.getCondition(), req.getTableId(), false);

      request.setJsonEntity("{\"query\":\"" + sql + "\"}");
      Response response = restHighLevelClient.getLowLevelClient().performRequest(request);
      ObjectMapper mapper = new ObjectMapper();
      JsonNode tree = mapper.readTree(response.getEntity().getContent());
      String queryStr = tree.get("query").toString();
      BoolQueryBuilder builder =
          QueryBuilders.boolQuery().must(QueryBuilders.wrapperQuery(queryStr));
      // 第二次相关性过滤
      if (CharSequenceUtil.isNotBlank(req.getTitle())) {
        List<String> split = StrUtil.split(req.getTitle(), " ");
        List<String> searchFieldList =
            getSearchFieldList(Collections.singletonList(req.getTableId()));
        for (String title : split) {
          BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery();
          searchFieldList.forEach(
              field -> {
                if (CharSequenceUtil.containsAnyIgnoreCase(field, "date")) {
                  shouldBuilder.should(
                      QueryBuilders.wildcardQuery(
                          field + "." + FieldType.KEYWORD.getType(), "*" + title + "*"));
                } else {
                  shouldBuilder.should(QueryBuilders.wildcardQuery(field, "*" + title + "*"));
                }
              });
          builder.must(shouldBuilder);
        }
      }
      // 年度过滤信息
      if (StrUtil.isNotBlank(req.getYears())) {
        List<Integer> years = IdUtil.split(req.getYears(), Integer.class);
        builder.must(QueryBuilders.termsQuery(ArchConst.FIELD_YEAR, years));
      }
      // 部门过滤信息
      if (StrUtil.isNotBlank(req.getDeptIds())) {
        Set<Long> deptIds = IdUtil.split(req.getDeptIds());
        builder.must(QueryBuilders.termsQuery(ArchConst.DEPARTMENT_ID, deptIds));
      }
      if (StrUtil.isNotBlank(req.getFileTypes())) {
        List<String> fileTypes = IdUtil.split(req.getFileTypes(), String.class);
        builder.must(QueryBuilders.termsQuery(ArchConst.FILE_TYPE, fileTypes));
      }
      //      // 添加查询权限设置
      String sourceQuery = buildAuth();
      builder.must(QueryBuilders.queryStringQuery(sourceQuery));
      BoolQueryBuilder search = QueryBuilders.boolQuery();
      search.must(builder);

      SearchSourceBuilder query = new SearchSourceBuilder().query(search);
      query.trackTotalHits(true);
      ImmutablePair<Integer, Integer> pair =
          buildPageInfo(Math.toIntExact(reqPage.getCurrent()), Math.toIntExact(reqPage.getSize()));
      query.from(pair.getLeft());
      query.size(pair.getRight());
      if (Objects.nonNull(req.getOrderByDate())) {
        if (ObjectUtil.equals(FieldOrderTypeEnum.ASC, req.getOrderByDate())) {
          query.sort("createTime", SortOrder.ASC);
        } else {
          query.sort("createTime", SortOrder.DESC);
        }
      }

      if (Objects.nonNull(req.getOrderByScore())) {
        query.sort("_score", SortOrder.DESC);
      }
      wrapper.setSearchSourceBuilder(query);
      wrapper.index(req.getTableId());

      BoolQueryBuilder optionBuilder =
          getConditionOptionQuery(req.getCondition(), req.getTableId());
      EsPageInfo<T> page =
          serviceEs.page(
              wrapper, Math.toIntExact(reqPage.getCurrent()), Math.toIntExact(reqPage.getSize()));
      List<OptionsModel<String>> fileTypeList =
          getOptionList(req, optionBuilder, ArchConst.FILE_TYPE, ArchConst.FILE_TYPE, serviceEs);
      List<OptionsModel<String>> yearList =
          getOptionList(req, optionBuilder, ArchConst.FIELD_YEAR, ArchConst.FIELD_YEAR, serviceEs);
      List<OptionsModel<String>> deptList =
          getOptionList(
              req, optionBuilder, ArchConst.DEPARTMENT_ID, ArchConst.DEPARTMENT, serviceEs);
      Class<? extends SystemBO> aClass = getArchClass(req.getTableId());
      List<T> list =
          buildHighlightAndFlag((List<? extends SystemBO>) page.getList(), (Class<T>) aClass);
      return AdvancedSearchVO.<T>builder()
          .list(list)
          .fillingList(fileTypeList)
          .deptList(deptList)
          .yearList(yearList)
          .total(page.getTotal())
          .build();
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw BizTipException.instance(ErrorCode.EXCEPTION, "请求失败");
    }
  }

  @NotNull
  private BoolQueryBuilder getConditionOptionQuery(
      List<ConditionBO> conditionList, String tableId) {
    try {
      BoolQueryBuilder builder = QueryBuilders.boolQuery();
      if (ObjectUtil.isNotEmpty(conditionList)) {
        Request request = new Request("POST", "/_sql/translate");
        String sql = QueryUtil.buildEsSql(conditionList, tableId, false);

        request.setJsonEntity("{\"query\":\"" + sql + "\"}");
        Response response = restHighLevelClient.getLowLevelClient().performRequest(request);
        ObjectMapper mapper = new ObjectMapper();
        JsonNode tree = mapper.readTree(response.getEntity().getContent());
        String queryStr = tree.get("query").toString();
        builder.must(QueryBuilders.wrapperQuery(queryStr));
      }

      // 添加查询权限设置
      String auth = buildAuth();
      builder.must(QueryBuilders.queryStringQuery(auth));
      return builder;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return QueryBuilders.boolQuery();
  }

  /**
   * 设置分页信息
   *
   * @param pageNum
   * @param pageSize
   * @return
   */
  private ImmutablePair<Integer, Integer> buildPageInfo(Integer pageNum, Integer pageSize) {
    pageNum =
        pageNum != null && pageNum > BaseEsConstants.ZERO ? pageNum : BaseEsConstants.PAGE_NUM;
    pageSize =
        pageSize != null && pageSize > BaseEsConstants.ZERO ? pageSize : BaseEsConstants.PAGE_SIZE;
    return new ImmutablePair<>((pageNum - 1) * pageSize, pageSize);
  }

  @NotNull
  private Class<? extends SystemBO> getArchClass(String tableId) {
    return ObjectUtil.contains(tableId, ArchTmplBO.DOCUMENT)
        ? DocumentEs.class
        : ObjectUtil.contains(tableId, ArchTmplBO.VOLUME)
            ? VolumeEs.class
            : ObjectUtil.contains(tableId, ArchTmplBO.BOX) ? BoxEs.class : ProjectEs.class;
  }

  @NotNull
  private HighlightBuilder getHighlightBuilder(List<String> keywords) {

    HighlightBuilder highlightBuilder = new HighlightBuilder();
    if (ObjectUtil.isEmpty(keywords)) {
      return highlightBuilder;
    }

    if (Objects.isNull(highlightFieldMap) || ObjectUtil.isEmpty(highlightFieldMap.keySet())) {
      return highlightBuilder;
    }

    highlightBuilder.preTags(ES_HIGHLIGHT_PRE_TAG);
    highlightBuilder.postTags(ES_HIGHLIGHT_POST_TAG);
    highlightBuilder.fragmentSize(Integer.valueOf(0));
    highlightBuilder.requireFieldMatch(false);
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

    keywords.forEach(
        key -> {
          highlightFieldMap
              .keySet()
              .forEach(field -> boolQuery.should(QueryBuilders.matchQuery(field, key)));
        });
    highlightBuilder.highlightQuery(boolQuery);
    return highlightBuilder;
  }

  private LambdaEsQueryWrapper<DocumentEs> buildQueryWrapper(
      QuickSearchDTO req, boolean filterIds) {
    if (ObjectUtil.equals(QuickSearchEnum.CATALOG, req.getType())) {
      return buildCatalogWrapper(req, filterIds);
    } else {
      // 原文检索
      return buildOriginQueryWrapper(req, filterIds);
    }
  }

  /**
   * 目录检索
   *
   * @param req
   * @param filterIds
   * @return
   */
  private LambdaEsQueryWrapper<DocumentEs> buildCatalogWrapper(
      QuickSearchDTO req, boolean filterIds) {
    /** 按空格分割检索内容 */
    List<String> keywordsList = StrUtil.split(req.getKeyword(), " ");

    LambdaEsQueryWrapper<DocumentEs> wrapper = new LambdaEsQueryWrapper<>();
    // 通过知悉范围及通用权限，直接通配符查询
    String catalogStr = "";

    List<String> searchFieldList = getSearchFieldList(null);
    if (BooleanUtil.isTrue(filterIds)) {
      // 第二次相关性过滤
      seTitleFilterCondition(req.getTitle(), wrapper, searchFieldList);
    }
    wrapper.index("f*document");
    wrapper.notSelect(FieldUtils.val(DocumentEs::getRecords));

    // 目录检索
    wrapper.notSelect(DocumentEs::getRecords);

    // 分词
    if (BooleanUtil.isTrue(req.getParticiple())) {
      // 多查询字段按 or连接
      String keywordsStr = CharSequenceUtil.join(" OR ", keywordsList);
      String multiMatchQuery =
          searchFieldList.stream()
              .map(field -> CharSequenceUtil.format(" {}:({}) ", field, keywordsStr))
              .collect(Collectors.joining(" OR "));
      catalogStr += CharSequenceUtil.format("({})", multiMatchQuery);
    } else {
      // 不分词,模糊查询
      // 多查询字段按 or连接
      String keywordsStr =
          keywordsList.stream()
              .map(keyword -> CharSequenceUtil.format("*{}*", keyword))
              .collect(Collectors.joining(" OR "));
      String multiMatchQuery =
          searchFieldList.stream()
              .map(
                  field -> {
                    String searchField = field;
                    if (ImmutableSet.of(ArchConst.ARCH_CODE, ArchConst.FIELD_TITLE)
                        .contains(field)) {
                      // 档号，题名特殊处理，使用keyword类型进行模糊匹配
                      searchField = field + EsConst.FIELD_KEYWORD_SUFFIX;
                    }
                    return CharSequenceUtil.format(" {}:({}) ", searchField, keywordsStr);
                  })
              .collect(Collectors.joining(" OR "));
      catalogStr += CharSequenceUtil.format(" ({}) ", multiMatchQuery);
    }

    if (Objects.nonNull(req.getOrderByDate())) {
      if (ObjectUtil.equals(FieldOrderTypeEnum.ASC, req.getOrderByDate())) {
        wrapper.orderByAsc(DocumentEs::getCreateTime);
      } else {
        wrapper.orderByDesc(DocumentEs::getCreateTime);
      }
    }

    if (Objects.nonNull(req.getOrderByScore())) {
      wrapper.sortByScore(SortOrder.DESC);
    }
    // 查询权限设置
    String authSql = buildAuth();
    String qrySql = CharSequenceUtil.format(" ({}) ", catalogStr);

    // 设置过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getFondsIds())) {
      qrySql = setFilterQuery(req.getFondsIds(), ArchConst.DEF_FONDS_ID_FIELD, qrySql);
    }
    // 年度过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getYears())) {
      qrySql = setFilterQuery(req.getYears(), ArchConst.FIELD_YEAR, qrySql);
    }
    // 部门过滤信息

    // 门类过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getArchTypeIds())) {
      qrySql = setFilterQuery(req.getArchTypeIds(), ArchConst.ARCH_TYPE_ID, qrySql);
    }

    wrapper.queryStringQuery(qrySql);
    //    buildAuth(wrapper);
    return wrapper;
  }

  /**
   * 条件过滤
   *
   * @param idsStr
   * @param fieldYear
   * @param qrySql
   * @return
   */
  private String setFilterQuery(String idsStr, String fieldYear, String qrySql) {
    Set<Long> ids = IdUtil.split(idsStr);
    String inFondsQry =
        CharSequenceUtil.format(" {}:({}) ", fieldYear, CharSequenceUtil.join(" OR ", ids));
    qrySql += CharSequenceUtil.format(" AND ({})", inFondsQry);
    return qrySql;
  }

  /**
   * 档案原文检索，条件构造
   *
   * @param req 请求参数
   * @param filterIds 过滤 左边导航 门类、全宗等过滤信息
   * @return
   */
  private LambdaEsQueryWrapper<DocumentEs> buildOriginQueryWrapper(
      QuickSearchDTO req, boolean filterIds) {
    BuildOriginQueryBuilder result = getBuildOriginQueryBuilder(req, filterIds);
    SearchSourceBuilder query = new SearchSourceBuilder().query(result.builder);
    query.trackTotalHits(true);
    ImmutablePair<Integer, Integer> pair =
        buildPageInfo(Math.toIntExact(req.getPage()), Math.toIntExact(req.getSize()));
    query.from(pair.getLeft());
    query.size(pair.getRight());
    String[] exclude = {"records"};
    String[] include = {};
    query.fetchSource(include, exclude);
    result.wrapper.setSearchSourceBuilder(query);
    return result.wrapper;
  }

  /**
   * 穿件原文查询，查询条件
   *
   * @param req
   * @param filterIds
   * @return
   */
  private BuildOriginQueryBuilder getBuildOriginQueryBuilder(
      QuickSearchDTO req, boolean filterIds) {
    /** 按空格分割检索内容 */
    List<String> keywordsList = StrUtil.split(req.getKeyword(), " ");

    LambdaEsQueryWrapper<DocumentEs> wrapper = new LambdaEsQueryWrapper<>();
    // 通过知悉范围及通用权限，直接通配符查询
    String catalogStr = "";
    List<String> searchFieldList = getSearchFieldList(null);
    if (BooleanUtil.isTrue(filterIds)) {
      // 第二次相关性过滤
      seTitleFilterCondition(req.getTitle(), wrapper, searchFieldList);
    }
    // 目录检索
    wrapper.notSelect(DocumentEs::getRecords);

    buildAuth(wrapper);

    BoolQueryBuilder builder = getSourceQuery(documentEsSvc.getBaseMapper().getSource(wrapper));
    wrapper.index("f*document");
    BoolQueryBuilder keywordBuilder = QueryBuilders.boolQuery();

    // 原文检索
    // 分词
    if (BooleanUtil.isTrue(req.getParticiple())) {
      String recordField = FieldUtils.val(DocumentEs::getRecords);
      String ocrContentField = FieldUtils.val(EFileRecordEs::getOcrContent);
      String titleField = FieldUtils.val(EFileRecordEs::getTitle);
      keywordsList.forEach(
          keyword -> {
            keywordBuilder
                .should(
                    QueryBuilders.matchQuery(recordField + "." + ocrContentField, keyword).boost(5))
                .should(QueryBuilders.matchQuery(recordField + "." + titleField, keyword).boost(2));
          });
    } else {
      // 不分词,模糊查询
      String recordField = FieldUtils.val(DocumentEs::getRecords);
      String ocrContentField = FieldUtils.val(EFileRecordEs::getOcrContentKw);
      String titleField = FieldUtils.val(EFileRecordEs::getTitleKw);
      keywordsList.forEach(
          keyword -> {
            keywordBuilder
                .should(
                    QueryBuilders.wildcardQuery(
                            recordField + "." + ocrContentField, "*" + keyword + "*")
                        .boost(5))
                .should(
                    QueryBuilders.wildcardQuery(recordField + "." + titleField, "*" + keyword + "*")
                        .boost(2));
          });
    }
    NestedQueryBuilder nestedBuilder =
        QueryBuilders.nestedQuery("records", keywordBuilder, ScoreMode.Max);
    // 高亮查询
    HighlightBuilder highlightBuilder =
        new HighlightBuilder()
            .field("records.ocrContent")
            .field("records.title")
            .noMatchSize(0)
            .numOfFragments(2)
            .order(HighlightBuilder.Order.SCORE)
            .preTags(ES_HIGHLIGHT_PRE_TAG)
            .postTags(ES_HIGHLIGHT_POST_TAG)
            .highlighterType(HighLightTypeEnum.UNIFIED.getValue());

    // 模糊查询，单独设置高亮查询信息，优化字段高亮
    if (!BooleanUtil.isTrue(req.getParticiple())) {
      BoolQueryBuilder hlQueryBuilder = QueryBuilders.boolQuery();
      keywordsList.forEach(
          keyword -> {
            hlQueryBuilder
                .should(QueryBuilders.matchQuery("records.ocrContent", keyword))
                .should(QueryBuilders.matchQuery("records.title", keyword));
          });

      highlightBuilder.highlightQuery(hlQueryBuilder);
    }
    String[] innerExclude = {"records.ocrContent", "records.ocrContentKw", "records.titleKw"};

    FetchSourceContext sourceContext = new FetchSourceContext(true, null, innerExclude);
    nestedBuilder
        .ignoreUnmapped(true)
        .innerHit(
            new InnerHitBuilder()
                .setFetchSourceContext(sourceContext)
                .setIgnoreUnmapped(true)
                .setHighlightBuilder(highlightBuilder));

    if (Objects.nonNull(req.getOrderByDate())) {
      if (ObjectUtil.equals(FieldOrderTypeEnum.ASC, req.getOrderByDate())) {
        wrapper.orderByAsc(DocumentEs::getCreateTime);
      } else {
        wrapper.orderByDesc(DocumentEs::getCreateTime);
      }
    }

    if (Objects.nonNull(req.getOrderByScore())) {
      wrapper.sortByScore(SortOrder.DESC);
    }
    // 查询权限设置
    String authSql = buildAuth();
    BoolQueryBuilder authBuilder =
        QueryBuilders.boolQuery().must(QueryBuilders.queryStringQuery(authSql)).must(nestedBuilder);
    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().should(authBuilder);

    // 特殊权限+查询信息
    builder.must(queryBuilder);

    // 过滤信息设置
    // 设置全宗过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getFondsIds())) {
      setFilterQueryBuilder(builder, req.getFondsIds(), ArchConst.DEF_FONDS_ID_FIELD);
    }
    // 年度过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getYears())) {
      setFilterQueryBuilder(builder, req.getYears(), ArchConst.FIELD_YEAR);
    }
    // 部门过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getDeptIds())) {
      setFilterQueryBuilder(builder, req.getDeptIds(), ArchConst.DEPARTMENT_ID);
    }
    // 门类过滤信息
    if (BooleanUtil.isTrue(filterIds) && CharSequenceUtil.isNotBlank(req.getArchTypeIds())) {
      setFilterQueryBuilder(builder, req.getArchTypeIds(), ArchConst.ARCH_TYPE_ID);
    }
    BuildOriginQueryBuilder result = new BuildOriginQueryBuilder(wrapper, builder);
    return result;
  }

  private static class BuildOriginQueryBuilder {
    public final LambdaEsQueryWrapper<DocumentEs> wrapper;
    public final BoolQueryBuilder builder;

    public BuildOriginQueryBuilder(
        LambdaEsQueryWrapper<DocumentEs> wrapper, BoolQueryBuilder builder) {
      this.wrapper = wrapper;
      this.builder = builder;
    }
  }

  /**
   * 设置过滤条件
   *
   * @param builder 查询构造器
   * @param idsStr 过滤id字符串
   * @param field 过滤字段
   */
  private void setFilterQueryBuilder(BoolQueryBuilder builder, String idsStr, String field) {
    Set<Long> ids = IdUtil.split(idsStr);
    String inFondsQry =
        CharSequenceUtil.format(" {}:({}) ", field, CharSequenceUtil.join(" OR ", ids));
    QueryStringQueryBuilder filterBuilder =
        QueryBuilders.queryStringQuery(CharSequenceUtil.format("({}) ", inFondsQry));
    builder.must(filterBuilder);
  }

  @NotNull
  private List<String> getSearchFieldList(List<String> tableList) {
    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectSearchList(tableList);
    if (ObjectUtil.isEmpty(fieldList)) {
      return new ArrayList<>(filterFieldSet);
    }
    AtomicBoolean archCodeFlag = new AtomicBoolean(false);
    List<String> list =
        fieldList.stream()
            .map(
                field -> {
                  String searchField = StringUtils.underlineToCamel(field.getDbFieldKey());
                  if (ImmutableSet.of(ArchConst.ARCH_CODE).contains(searchField)) {
                    archCodeFlag.set(true);
                  }
                  return searchField;
                })
            .collect(Collectors.toList());
    if (archCodeFlag.get()) {
      list.add(ArchConst.ARCH_CODE + EsConst.FIELD_KEYWORD_SUFFIX);
    }
    return list;
  }

  private List<OptionsModel<String>> getFondsList(QuickSearchDTO req) {
    LambdaEsQueryWrapper<DocumentEs> group = buildQueryWrapper(req, false);
    group.groupBy(DocumentEs::getFondsId);
    group.distinct(DocumentEs::getFondsId);
    List<DocumentEs> list = documentEsSvc.list(group);
    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }

    List<Long> fondsIds = list.stream().map(DocumentEs::getFondsId).collect(Collectors.toList());
    List<SysFonds> sysFonds = sysFondsSvc.listByIds(fondsIds);
    return sysFonds.stream()
        .map(
            fonds -> {
              OptionsModel<String> model = new OptionsModel<>();
              model.setValue(String.valueOf(fonds.getId()));
              model.setLabel(fonds.getName());
              return model;
            })
        .collect(Collectors.toList());
  }

  /**
   * 获取左侧分组信息
   *
   * @param req
   * @param fieldName
   * @param labelFieldName
   * @return
   */
  private List<OptionsModel<String>> getOptionList(
      QuickSearchDTO req, List<Long> archTypes, String fieldName, String labelFieldName) {
    List<DocumentEs> list = Lists.newArrayList();
    // 原文检索
    if (ObjectUtil.equals(QuickSearchEnum.ORIGINAL, req.getType())) {
      BuildOriginQueryBuilder queryBuilder = getBuildOriginQueryBuilder(req, false);
      BoolQueryBuilder builder = queryBuilder.builder;
      LambdaEsQueryWrapper<DocumentEs> wrapper = queryBuilder.wrapper;
      SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(builder);
      sourceBuilder.collapse(new CollapseBuilder(fieldName));
      String[] fields = {fieldName, labelFieldName, ArchConst.COM_NAME};
      sourceBuilder.fetchSource(fields, null);
      wrapper.setSearchSourceBuilder(sourceBuilder);
      list = documentEsSvc.list(wrapper);
    } else {
      LambdaEsQueryWrapper<DocumentEs> group = buildQueryWrapper(req, false);
      group.groupBy(fieldName);
      group.distinct(fieldName);
      group.select(fieldName, labelFieldName, ArchConst.COM_NAME);
      // 限定门类条件
      if (!archTypes.isEmpty()) {
        group.in(ArchConst.ARCH_TYPE_ID, archTypes);
      }
      list = documentEsSvc.list(group);
    }

    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }

    return list.stream()
        .map(
            doc -> {
              OptionsModel<String> model = new OptionsModel<>();
              JSONObject parseObj = JSONUtil.parseObj(doc);
              String value = parseObj.getStr(fieldName);
              String label = parseObj.getStr(labelFieldName);
              // 过滤信息非年度并且不为空数据，添加全宗信息标识 如： 文书档案(默认全宗)
              if (!CharSequenceUtil.equals(fieldName, ArchConst.FIELD_YEAR)
                  && ObjectUtil.isNotNull(value)) {
                label += "(" + parseObj.getStr(ArchConst.COM_NAME) + ")";
              }
              model.setValue(Optional.ofNullable(value).orElse(null));
              model.setLabel(Optional.ofNullable(label).orElse("--"));
              return model;
            })
        .collect(Collectors.toList());
  }

  /**
   * 通过条件，查询左侧分组信息
   *
   * @param req 请求信息
   * @param builder 查询条件
   * @param fieldName value字段名
   * @param labelFieldName label 字段名
   * @param serviceEs es 服务
   * @param <T> 类型
   * @return
   */
  @SneakyThrows
  private <T> List<OptionsModel<String>> getOptionList(
      AdvancedSearchDTO req,
      BoolQueryBuilder builder,
      String fieldName,
      String labelFieldName,
      IServiceEs serviceEs) {
    SearchSourceBuilder query = new SearchSourceBuilder().query(builder);
    if (Objects.nonNull(req.getOrderByDate())) {
      if (ObjectUtil.equals(FieldOrderTypeEnum.ASC, req.getOrderByDate())) {
        query.sort("createTime", SortOrder.ASC);
      } else {
        query.sort("createTime", SortOrder.DESC);
      }
    }

    if (Objects.nonNull(req.getOrderByScore())) {
      query.sort("_score", SortOrder.DESC);
    }
    query.collapse(new CollapseBuilder(fieldName));
    LambdaEsQueryWrapper<T> wrapper = new LambdaEsQueryWrapper<>();
    wrapper.setSearchSourceBuilder(query);
    wrapper.index(req.getTableId());
    List<T> list = serviceEs.list(wrapper);
    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }

    return list.stream()
        .map(
            doc -> {
              OptionsModel<String> model = new OptionsModel<>();
              JSONObject parseObj = JSONUtil.parseObj(doc);
              String value = parseObj.getStr(fieldName);
              String label = parseObj.getStr(labelFieldName);
              model.setValue(Optional.ofNullable(value).orElse(null));
              model.setLabel(Optional.ofNullable(label).orElse("--"));
              return model;
            })
        .collect(Collectors.toList());
  }

  @Override
  public CardSearchVO cardSearch(CardSearchDTO req) {
    Page reqPage = new Page<>(req.getPage(), req.getSize());
    String tableId = req.getTableId();
    if (tableId.contains(ArchTmplBO.DOCUMENT)) {
      return byCard(reqPage, documentEsSvc, req);
    } else if (tableId.contains(ArchTmplBO.VOLUME)) {
      return byCard(reqPage, volumeEsSvc, req);
    }
    throw BizTipException.instance(ErrorCode.NOT_FOUND, "未知档案类型");
  }

  /**
   * 卡片检索
   *
   * @param reqPage 分页
   * @param serviceEs 对应的es service
   * @param req 请求参数
   * @param <T> 结果类型
   * @return
   */
  public <T> CardSearchVO byCard(Page reqPage, IServiceEs serviceEs, CardSearchDTO req) {
    try {
      LambdaEsQueryWrapper<Object> wrapper = buildCardQuery(req);
      wrapper.index(req.getTableId());
      EsPageInfo<T> page =
          serviceEs.page(
              wrapper, Math.toIntExact(reqPage.getCurrent()), Math.toIntExact(reqPage.getSize()));
      List<OptionsModel<String>> fileTypeList =
          getOptionList(req, ArchConst.FILE_TYPE, ArchConst.FILE_TYPE, documentEsSvc);
      List<OptionsModel<String>> yearList =
          getOptionList(req, ArchConst.FIELD_YEAR, ArchConst.FIELD_YEAR, documentEsSvc);
      List<OptionsModel<String>> deptList =
          getOptionList(req, ArchConst.DEPARTMENT_ID, ArchConst.DEPARTMENT, documentEsSvc);
      List<OptionsModel<String>> modelList =
          getOptionList(req, ArchConst.ARCH_MODEL, ArchConst.ARCH_MODEL_NAME, documentEsSvc);
      List<T> list =
          buildHighlightAndFlag(
              (List<? extends SystemBO>) page.getList(), (Class<T>) getArchClass(req.getTableId()));
      return CardSearchVO.<T>builder()
          .list(list)
          .fileTypeList(fileTypeList)
          .deptList(deptList)
          .yearList(yearList)
          .modelList(modelList)
          .total(page.getTotal())
          .build();
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw BizTipException.instance(ErrorCode.EXCEPTION, "请求失败");
    }
  }

  /**
   * 拼接卡片检索条件
   *
   * @param req
   * @param <T>
   * @return
   */
  private <T> LambdaEsQueryWrapper<T> buildCardQuery(CardSearchDTO req) {
    LambdaEsQueryWrapper<T> wrapper = new LambdaEsQueryWrapper<>();
    Map<String, Object> condition = req.getCondition();
    if (Objects.nonNull(condition)) {
      // 拼接条件
      Map<String, Object> sourceAsMap =
          documentEsSvc
              .getBaseMapper()
              .getIndex(req.getTableId())
              .getMappings()
              .get(req.getTableId())
              .getSourceAsMap();
      Object o = sourceAsMap.get("properties");
      Map<String, Object> properties = BeanUtil.beanToMap(o);
      condition.forEach(
          (key, value) -> {
            if (ObjectUtil.isEmpty(value)) {
              return;
            }
            if (ImmutableSet.of("startYear", "endYear", "startDocDate", "endDocDate")
                .contains(key)) {
              if (ObjectUtil.equals("startYear", key)
                  && ObjectUtil.isAllNotEmpty(
                      condition.get("startYear"), condition.get("endYear"))) {
                wrapper.between(
                    ArchConst.FIELD_YEAR, condition.get("startYear"), condition.get("endYear"));
              }
              if (ObjectUtil.equals("startDocDate", key)
                  && ObjectUtil.isAllNotEmpty(
                      condition.get("startDocDate"), condition.get("endDocDate"))) {
                wrapper.between(
                    FieldUtils.val(DocumentEs::getDocDate),
                    condition.get("startDocDate"),
                    condition.get("endDocDate"));
              }
            } else {
              Object property = properties.get(key);
              JSONObject parseObj = JSONUtil.parseObj(property);
              String type = parseObj.getStr("type");
              if (ImmutableSet.of(FieldType.TEXT.getType(), FieldType.KEYWORD_TEXT.getType())
                  .contains(type)) {
                wrapper.like(key, value);
              } else {
                wrapper.eq(key, value);
              }
            }
          });
    }

    // 年度过滤信息
    if (StrUtil.isNotBlank(req.getYears())) {
      List<Integer> years = IdUtil.split(req.getYears(), Integer.class);
      wrapper.in(ArchConst.FIELD_YEAR, years);
    }
    // 部门过滤信息
    if (StrUtil.isNotBlank(req.getDeptIds())) {
      Set<Long> deptIds = IdUtil.split(req.getDeptIds());
      wrapper.in(ArchConst.DEPARTMENT_ID, deptIds);
    }
    if (StrUtil.isNotBlank(req.getFileTypes())) {
      List<String> fileTypes = IdUtil.split(req.getFileTypes(), String.class);
      wrapper.in(ArchConst.FILE_TYPE, fileTypes);
    }
    if (StrUtil.isNotBlank(req.getModel())) {
      List<String> modelList = IdUtil.split(req.getModel(), String.class);
      wrapper.in(ArchConst.ARCH_MODEL, modelList);
    }

    buildAuth(wrapper);
    // 特殊利用权限
    String specialAuth = conditionCmd.getSpecialAuth();
    if (CharSequenceUtil.isNotBlank(specialAuth)) {
      wrapper.or().queryStringQuery(specialAuth);
    }

    return wrapper;
  }

  private <T> void buildAuth(LambdaEsQueryWrapper<T> wrapper) {
    // 权限设置
    // 设置门类权限
    List<Long> archTypes = searchAuthCmd.getArchTypeByUser(SecurityUtil.getUserId());
    // 查询指定档案ID集合
    List<Long> archIds = searchAuthCmd.getArchByUser(SecurityUtil.getUserId());
    // 如果是非管理员，添加此检索限制
    if (!BooleanUtil.isTrue(SecurityUtil.isRootGroup())) {
      List<Integer> ctls = searchAuthCmd.getAuthRangeByUser(SecurityUtil.getUserId());
      if ((ObjectUtil.isEmpty(archTypes) || archTypes.isEmpty())) {
        throw BizTipException.instance(ErrorCode.VALIDATE_ERROR, "当前用户未授权，请联系管理员授权！");
      }
      // 查询指定门类及开放档案
      wrapper.in(ArchConst.ARCH_TYPE_ID, archTypes);
      if (ObjectUtil.isNotEmpty(ctls)) {
        wrapper.in(ArchConst.CONTROL_IDENTIFIER, ctls);
      }
      // 档案库权限设置
      if (ObjectUtil.isNotEmpty(archIds)) {
        wrapper.in(ArchConst.DEF_ID, archIds);
      }
      wrapper.eq(ArchConst.PUBLISH_STATUS, ArchControlConst.PUBLISH_YES);
    }
  }

  private String buildAuth() {
    // 权限设置
    // 知悉范围内，权限查询语句
    String inScopeAuthSql = CharSequenceUtil.format(" onlineBrowser:true ");

    String authSql = CharSequenceUtil.format("{}", inScopeAuthSql);
    // 特殊权限设置
    String specialAuth = conditionCmd.getSpecialAuth();

    if (CharSequenceUtil.isNotBlank(specialAuth)) {
      return CharSequenceUtil.format(" (({}) OR {}) ", authSql, specialAuth);
    } else {
      return CharSequenceUtil.format(" ({}) ", authSql);
    }
  }

  /**
   * 通过条件，查询左侧分组信息
   *
   * @param req 请求信息
   * @param fieldName value字段名
   * @param labelFieldName label 字段名
   * @param serviceEs es 服务
   * @param <T> 类型
   * @return
   */
  @SneakyThrows
  private <T> List<OptionsModel<String>> getOptionList(
      CardSearchDTO req, String fieldName, String labelFieldName, IServiceEs serviceEs) {
    LambdaEsQueryWrapper<Object> wrapper = buildCardQuery(req);
    wrapper.groupBy(fieldName);
    wrapper.distinct(fieldName);
    wrapper.index(req.getTableId());
    List<T> list = serviceEs.list(wrapper);
    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }

    return list.stream()
        .map(
            doc -> {
              OptionsModel<String> model = new OptionsModel<>();
              JSONObject parseObj = JSONUtil.parseObj(doc);
              String value = parseObj.getStr(fieldName);
              String label = parseObj.getStr(labelFieldName);
              model.setValue(Optional.ofNullable(value).orElse(null));
              model.setLabel(Optional.ofNullable(label).orElse("--"));
              return model;
            })
        .collect(Collectors.toList());
  }

  @Override
  public CatalogSearchVO catalogSearch(CatalogSearchDTO req) {
    ServiceInfo serviceInfo = getServiceInfo(req.getTableId());
    return byCatalog(req, serviceInfo.serviceEs);
  }

  private <T> CatalogSearchVO byCatalog(CatalogSearchDTO req, IServiceEs serviceEs) {
    LambdaEsQueryWrapper<T> wrapper = new LambdaEsQueryWrapper<>();

    List<String> searchFieldList = getSearchFieldList(Collections.singletonList(req.getTableId()));
    // 第二次相关性过滤
    seTitleFilterCondition(req.getTitle(), wrapper, searchFieldList);
    if (ObjectUtil.isNotNull(req.getProjId())) {
      wrapper.eq(ArchConst.PROJ_ID, req.getProjId());
    }
    if (ObjectUtil.isNotNull(req.getVolId())) {
      wrapper.eq(ArchConst.VOL_ID, req.getVolId());
    }
    IAuthDetailsSvc authDetailsSvc = SpringUtil.getBean(IAuthDetailsSvc.class);
    LambdaQueryWrapper<AuthDetails> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.eq(AuthDetails::getUserId, SecurityUtil.getUserId());
    List<AuthDetails> authDetails = authDetailsSvc.list(queryWrapper);
    List<Long> docIds = authDetails.stream().map(AuthDetails::getObjId).collect(Collectors.toList());
    if (docIds.isEmpty()) {
      return CatalogSearchVO.builder().list(Lists.newArrayList()).total(1L).build();
    }
    // 查询个人权限档案 过滤档案
    wrapper.in("_id", docIds);

    // 数据节点条件设置
    Map<String, String> groupCondition =
        Optional.ofNullable(req.getGroupCondition()).orElse(Maps.newHashMap());
    if (ObjectUtil.isNotEmpty(groupCondition)) {
      groupCondition.forEach(
          (key, value) -> {
            // 空值查询
            if (CharSequenceUtil.equals(Constants.QUERY_NULL_DATA_PLACEHOLDER, value)) {
              wrapper.eq(key, "");
            } else {
              // 非空值查询
              wrapper.eq(key, value);
            }
          });
    }

    wrapper.index(req.getTableId());
    //     查询权限设置
    //    buildAuth(wrapper);
    BoolQueryBuilder optionBuilder = getConditionOptionQuery(req.getCondition(), req.getTableId());
    BoolQueryBuilder builder = getSourceQuery(serviceEs.getBaseMapper().getSource(wrapper));
    builder.must(optionBuilder);
    SearchSourceBuilder query = new SearchSourceBuilder().query(builder);
    CommonQueryCmd commonQueryCmd = SpringUtil.getBean(CommonQueryCmd.class);
    // 排序设置
    commonQueryCmd.buildEsFieldOrderQuery(req, query);
    // 分页不准问题
    query.trackTotalHits(true);
    wrapper.setSearchSourceBuilder(query);

    EsPageInfo page = serviceEs.page(wrapper, req.getPage(), req.getSize());
    List list = buildHighlightAndFlag(page.getList(), (Class<T>) getArchClass(req.getTableId()));
    return CatalogSearchVO.builder().list(list).total(page.getTotal()).build();
  }

  private static <T> BoolQueryBuilder getSourceQuery(String query) {
    try {
      ObjectMapper mapper = new ObjectMapper();
      JsonNode tree = mapper.readTree(query);

      String queryStr = tree.get("query").toString();
      return QueryBuilders.boolQuery().must(QueryBuilders.wrapperQuery(queryStr));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return QueryBuilders.boolQuery();
  }

  private static <T> void seTitleFilterCondition(
      String filterTitle, LambdaEsQueryWrapper<T> wrapper, List<String> searchFieldList) {
    if (CharSequenceUtil.isBlank(filterTitle)) {
      return;
    }
    List<String> split = StrUtil.split(filterTitle, " ");
    split.forEach(
        title ->
            wrapper.and(
                c -> {
                  c.and(
                      sub -> {
                        AtomicInteger index = new AtomicInteger(1);
                        searchFieldList.forEach(
                            field -> {
                              if (CharSequenceUtil.containsAnyIgnoreCase(field, "date")) {
                                sub.like(field + "." + FieldType.KEYWORD.getType(), title);
                              } else {
                                sub.like(field, title);
                              }
                              // 最后拼接or会导致多条件过滤拼接为should，不拼接最后一个or条件
                              int current = index.getAndIncrement();
                              if (current != searchFieldList.size()) {
                                sub.or();
                              }
                            });
                      });
                }));
  }

  private ServiceInfo getServiceInfo(String tableId) {

    if (tableId.contains(ArchTmplBO.DOCUMENT)) {
      return new ServiceInfo(documentEsSvc, DocumentEs.class);
    }
    if (tableId.contains(ArchTmplBO.VOLUME)) {
      return new ServiceInfo(volumeEsSvc, VolumeEs.class);
    }
    if (tableId.contains(ArchTmplBO.BOX)) {

      return new ServiceInfo(boxEsSvc, BoxEs.class);
    }
    if (tableId.contains(ArchTmplBO.PROJECT)) {
      return new ServiceInfo(projectEsSvc, ProjectEs.class);
    }
    return new ServiceInfo();
  }

  @Override
  public List<Tree<String>> catalogTree(CatalogSearchTreeDTO req) {
    // 按公司id查询全宗树
    if (Objects.nonNull(req.getComId())) {
      return getFondsTree(req);
    }
    // 按全宗id查询门类树
    if (Objects.nonNull(req.getFondsId())) {
      return getArchTypeList(req);
    }
    if (ObjectUtil.equals(2, req.getType())) {
      ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
      List<TreeNode<String>> treeNodeList = Lists.newArrayList();
      TreeNode<String> docNode =
          buildTreeNode(archiveType.getDocTableId(), archiveType.getId().toString(), 1);
      treeNodeList.add(docNode);
      if (BooleanUtil.isTrue(archiveType.getHaveVol())) {
        TreeNode<String> volNode =
            buildTreeNode(archiveType.getVolTableId(), archiveType.getId().toString(), 2);
        treeNodeList.add(volNode);
      }
      return TreeUtil.build(treeNodeList, "0");
    }
    // 按门类信息查询门类目录树
    return getCatalogTree(req);
  }

  private TreeNode<String> buildTreeNode(String tableId, String archTypeId, Integer sort) {
    TreeNode<String> treeNode = new TreeNode();
    treeNode.setId(tableId);
    String name = ObjectUtil.contains(tableId, ArchTmplBO.DOCUMENT) ? "文件" : "案卷";
    treeNode.setName(name);
    treeNode.setParentId("0");
    treeNode.setWeight(sort);
    ImmutableMap<String, String> condition =
        ImmutableMap.of("tableId", tableId, "archTypeId", archTypeId);
    treeNode.setExtra(ImmutableMap.of("dataCondition", JSONUtil.toJsonStr(condition)));
    return treeNode;
  }

  /**
   * 获取公司下的所有全宗列表
   *
   * @param req
   * @return
   */
  private List<Tree<String>> getFondsTree(CatalogSearchTreeDTO req) {
    List<SysFondsListVO> list = sysFondsSvc.list(req.getComId());
    List<TreeNode<String>> treeNodeList =
        list.stream()
            .map(
                item -> {
                  TreeNode<String> treeNode = new TreeNode();
                  treeNode.setId(item.getId().toString());
                  treeNode.setName(item.getName());
                  treeNode.setParentId("0");
                  treeNode.setWeight(item.getSort());
                  ImmutableMap<String, String> condition =
                      ImmutableMap.of("fondsId", item.getId().toString());
                  treeNode.setExtra(
                      ImmutableMap.of("dataCondition", JSONUtil.toJsonStr(condition)));
                  return treeNode;
                })
            .collect(Collectors.toList());
    return TreeUtil.build(treeNodeList, "0");
  }

  /**
   * 门类列表
   *
   * @param req
   * @return
   */
  private List<Tree<String>> getArchTypeList(CatalogSearchTreeDTO req) {
    ArchiveTypeQueryDTO qry = new ArchiveTypeQueryDTO();
    qry.setFondsId(req.getFondsId());
    List<ArchiveTypeListVO> list = archiveTypeSvc.selectList(qry);
    List<TreeNode<String>> treeNodeList =
        list.stream()
            .map(
                item -> {
                  TreeNode<String> treeNode = new TreeNode();
                  treeNode.setId(item.getId().toString());
                  treeNode.setName(item.getName());
                  treeNode.setParentId("0");
                  treeNode.setWeight(item.getSort());
                  String tableId =
                      item.getHaveProject()
                          ? item.getProjTableId()
                          : item.getHaveVol() ? item.getVolTableId() : item.getDocTableId();
                  ImmutableMap<String, String> condition =
                      ImmutableMap.of(
                          ArchConst.ARCH_TYPE_ID,
                          item.getId().toString(),
                          ArchConst.TABLE_ID,
                          tableId);
                  treeNode.setExtra(
                      ImmutableMap.of("dataCondition", JSONUtil.toJsonStr(condition)));
                  return treeNode;
                })
            .collect(Collectors.toList());
    return TreeUtil.build(treeNodeList, "0");
  }

  /**
   * 门类目录树
   *
   * @param req
   * @return
   */
  private List<Tree<String>> getCatalogTree(CatalogSearchTreeDTO req) {
    CatalogTreeQueryDTO qry = new CatalogTreeQueryDTO();
    qry.setTableId(req.getTableId());

    if (req.getTableId().contains(ArchTmplBO.PROJECT)) {
      qry.setManageType(null);
    } else {
      qry.setManageType(ArchStatusEnum.S1.getCode());
    }
    long pid = Objects.isNull(req.getPid()) ? 0L : req.getPid();
    qry.setPid(pid);
    List<Tree<String>> tree = catalogSvc.tree(qry, false);
    tree.forEach(
        node -> {
          Object dataCondition = node.get("dataCondition");
          JSONObject parseObj = JSONUtil.parseObj(dataCondition);
          parseObj.set(ArchConst.ARCH_TYPE_ID, req.getArchTypeId());
          parseObj.set(ArchConst.TABLE_ID, req.getTableId());
          parseObj.set(ArchConst.DEF_STATUS, qry.getManageType());
          parseObj.set("pid", node.getId());
          node.putExtra("dataCondition", parseObj.toString());
        });
    return tree;
  }

  @Override
  public SearchViewVO view(SearchViewDTO req) {
    if (req.getTableId().contains(ArchTmplBO.DOCUMENT)) {
      DocumentEs data =
          Optional.ofNullable(documentEsSvc.getById(req.getId(), req.getTableId()))
              .orElseThrow(
                  () -> BizTipException.instance(ErrorCode.USE_ARCH_DATA_NOT_FOUND, "未找到条目"));
      // 设置收藏标识
      data.setCollectFlag(getFavoritesFlag(data.getArchTypeId(), data.getTableId(), data.getId()));
      // 设置借阅车
      data.setBorrowCarFlag(
          getBorrowCarFlag(data.getArchTypeId(), data.getTableId(), data.getId()));
      SearchViewVO vo = SearchViewVO.builder().data(data).build();
      // 电子文件
      setEfileTableName(data);
      List<EfileRecordListVO> fileList =
          efileRecordSvc.selectListByDocIds(req.getArchTypeId(), ImmutableSet.of(req.getId()));
      vo.setFileList(fileList);
      // 关联档案
      ArchiveRelationArchQueryDTO qryArch = new ArchiveRelationArchQueryDTO();
      qryArch.setArchTypeId(req.getArchTypeId());
      qryArch.setTableId(req.getTableId());
      qryArch.setDataId(req.getId());
      List<ArchiveRelationListVO> relationList = archiveRelationSvc.archList(qryArch);
      vo.setRelationList(relationList);
      // 记录使用日志
      amsEventPublisher.publishEvent(
          StatisticsUsingOpEvent.builder()
              .comId(SecurityUtil.getCropId())
              .fondsId(SecurityUtil.getFondsId())
              .archTypeId(req.getArchTypeId())
              .tableId(req.getTableId())
              .archId(req.getId())
              .userName(SecurityUtil.getUsername())
              .op(UseTypeEnum.ONLINE_BROWSER.getType())
              .realOp(UseTypeEnum.ONLINE_BROWSER.getType())
              .archTitle(data.getTitle())
              .deptId(SecurityUtil.getLoginUser().getDeptId())
              .build());
      return vo;
    } else {
      ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
      VolumeEs volumeEs = volumeEsSvc.getById(req.getId(), req.getTableId());
      // 设置收藏标识
      volumeEs.setCollectFlag(
          getFavoritesFlag(volumeEs.getArchTypeId(), volumeEs.getTableId(), volumeEs.getId()));
      // 设置借阅车
      volumeEs.setBorrowCarFlag(
          getBorrowCarFlag(volumeEs.getArchTypeId(), volumeEs.getTableId(), volumeEs.getId()));
      // 查询文档列表
      LambdaEsQueryWrapper<DocumentEs> wrapper = new LambdaEsQueryWrapper<>();
      wrapper.eq(DocumentEs::getVolId, volumeEs.getId());
      wrapper.index(archiveType.getDocTableId());
      wrapper.size(Integer.MAX_VALUE);
      List<DocumentEs> docList = documentEsSvc.list(wrapper);
      SearchViewVO vo = SearchViewVO.builder().data(volumeEs).build();
      vo.setDocList(docList);
      // 记录使用日志
      amsEventPublisher.publishEvent(
          StatisticsUsingOpEvent.builder()
              .comId(SecurityUtil.getCropId())
              .fondsId(SecurityUtil.getFondsId())
              .archTypeId(req.getArchTypeId())
              .tableId(req.getTableId())
              .archId(req.getId())
              .userName(SecurityUtil.getUsername())
              .op(UseTypeEnum.ONLINE_BROWSER.getType())
              .realOp(UseTypeEnum.ONLINE_BROWSER.getType())
              .deptId(SecurityUtil.getLoginUser().getDeptId())
              .build());
      return vo;
    }
  }

  @Override
  public EfileRecordVO download(SearchFileDownloadDTO req) {
    DocumentEs data = documentEsSvc.getById(req.getId(), req.getTableId());
    UseTypeEnum useTypeEnum = UseTypeEnum.DOWNLOAD;
    if (BooleanUtil.isTrue(req.getOriginFlag())) {
      useTypeEnum = UseTypeEnum.DOWNLOAD_SOURCE;
    }
    boolean auth = authCmd.checkAuth(req.getArchTypeId(), data, useTypeEnum);
    if (!auth) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_AUTH, "无权限");
    }
    setEfileTableName(data);
    EfileRecordVO view = efileRecordSvc.view(req.getFileId());
    if (!BooleanUtil.isTrue(req.getOriginFlag())
        && CharSequenceUtil.isBlank(view.getShowFilePath())) {
      throw BizTipException.instance(ErrorCode.VALIDATE_ERROR, "当前文件无法下载水印版");
    }

    if (!BooleanUtil.isTrue(req.getOriginFlag())) {
      String root =
          efileRecordSvc.getBasePathByArrangeStatus(view.getDefPath(), view.getArrangeStatus());
      String filePath = root + view.getShowFilePath();
      view.setFile(FileUtil.file(filePath));
      efileRecordSvc.addWatermark(view, useTypeEnum.getType());
      File file =
          ossTemplate.getFile(
              efileRecordSvc.getBasePathByArrangeStatus(view.getDefPath(), view.getArrangeStatus())
                  + view.getShowFilePath());
      view.setFile(file);
      view.setTitle(file.getName());
    }

    // 记录使用日志
    amsEventPublisher.publishEvent(
        StatisticsUsingOpEvent.builder()
            .comId(SecurityUtil.getCropId())
            .fondsId(SecurityUtil.getFondsId())
            .archTypeId(req.getArchTypeId())
            .tableId(req.getTableId())
            .archId(req.getId())
            .userName(SecurityUtil.getUsername())
            .op(UseTypeEnum.DOWNLOAD.getType())
            .realOp(UseTypeEnum.DOWNLOAD.getType())
            .deptId(SecurityUtil.getLoginUser().getDeptId())
            .build());
    return view;
  }

  @Override
  public OssFile downloadZip(SearchFileZipDownloadDTO req) {
    DocumentEs data = documentEsSvc.getById(req.getId(), req.getTableId());
    if (ObjectUtil.isEmpty(data)) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_ARCH, "未找到档案");
    }
    UseTypeEnum useTypeEnum =
        BooleanUtil.isTrue(req.getOriginFlag())
            ? UseTypeEnum.DOWNLOAD_SOURCE
            : UseTypeEnum.DOWNLOAD;
    boolean auth = authCmd.checkAuth(req.getArchTypeId(), data, useTypeEnum);
    if (!auth) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_AUTH, "无权限");
    }
    EfileRecordQueryDTO qry = new EfileRecordQueryDTO();
    qry.setDocIds(req.getId().toString());
    setEfileTableName(data);
    OssFile ossFile = efileRecordSvc.exportZip(qry);
    // 记录使用日志
    amsEventPublisher.publishEvent(
        StatisticsUsingOpEvent.builder()
            .comId(SecurityUtil.getCropId())
            .fondsId(SecurityUtil.getFondsId())
            .archTypeId(req.getArchTypeId())
            .tableId(req.getTableId())
            .archId(req.getId())
            .userName(SecurityUtil.getUsername())
            .op(UseTypeEnum.DOWNLOAD_SOURCE.getType())
            .realOp(UseTypeEnum.DOWNLOAD_SOURCE.getType())
            .deptId(SecurityUtil.getLoginUser().getDeptId())
            .build());
    return ossFile;
  }

  @Override
  public EfileRecordVO preview(SearchFilePreviewDTO req) {
    DocumentEs data = documentEsSvc.getById(req.getId(), req.getTableId());
    boolean auth = authCmd.checkAuth(req.getArchTypeId(), data, UseTypeEnum.ONLINE_BROWSER);
    if (!auth) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_AUTH, "无权限");
    }
    EfileRecordQueryDTO qry = new EfileRecordQueryDTO();
    qry.setDocIds(req.getId().toString());
    boolean addWatermark = true;
    if (BooleanUtil.isTrue(req.getExtractFlag())
        && !authCmd.checkAuth(req.getArchTypeId(), data, UseTypeEnum.EXTRACT)) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_AUTH, "无权限");
    }
    if (BooleanUtil.isTrue(req.getExtractFlag())
        && authCmd.checkAuth(req.getArchTypeId(), data, UseTypeEnum.EXTRACT)) {
      addWatermark = false;
    }
    setEfileTableName(data);
    EfileRecordVO vo =
        efileRecordSvc.pdfPreview(
            req.getFileId(), UseTypeEnum.ONLINE_BROWSER.getType(), addWatermark);
    //    // 设置水印
    //    String showFilePath = vo.getShowFilePath();
    //    String basePath =ossTemplate.getSystemTempPath();
    //    String waterMarkPath =
    //        basePath + efileRecordSvc.newFileNameAndPath(showFilePath, "wm", vo.getFile());
    //    WatermarkVO watermarkVO =
    //        watermarkSvc.addWatermark(vo.getArchTypeId(), vo.getFile(), waterMarkPath);
    if (Objects.nonNull(vo.getFile())) {
      vo.setFile(vo.getFile());
      vo.setShowFilePath(vo.getShowFilePath());
    }
    // 记录使用日志
    amsEventPublisher.publishEvent(
        StatisticsUsingOpEvent.builder()
            .comId(SecurityUtil.getCropId())
            .fondsId(SecurityUtil.getFondsId())
            .archTypeId(req.getArchTypeId())
            .tableId(req.getTableId())
            .archId(req.getId())
            .userName(SecurityUtil.getUsername())
            .op(UseTypeEnum.ONLINE_BROWSER.getType())
            .realOp(UseTypeEnum.ONLINE_BROWSER.getType())
            .deptId(SecurityUtil.getLoginUser().getDeptId())
            .build());
    return vo;
  }

  private static void setEfileTableName(DocumentEs data) {
    EfileTableNameUtil efileTableNameUtil = SpringUtil.getBean(EfileTableNameUtil.class);
    String tableName = efileTableNameUtil.getTableName(data.getFondsId());
    DynamicTableNameUtil.setTableName(tableName);
  }

  @Override
  public boolean checkAuth(SearchAuthCheckDTO req) {
    DocumentEs data = documentEsSvc.getById(req.getId(), req.getTableId());
    return authCmd.checkAuth(req.getArchTypeId(), data, req.getUseTypeEnum());
  }

  @Override
  public SearchAuthVO getAuth(SearchAuthQueryDTO req) {
    DocumentEs data = documentEsSvc.getById(req.getId(), req.getTableId());
    if (ObjectUtil.isEmpty(data)) {
      return new SearchAuthVO();
      //      throw BizTipException.instance(ErrorCode.SEARCH_NO_ARCH, "未找到档案信息");
    }
    return authCmd.getAuth(req.getArchTypeId(), data);
  }

  @Override
  public List<DocumentEs> quickSearchData(QuickSearchExportDTO req) {
    List<DocumentEs> res = Lists.newArrayList();
    // 全部导出
    if (ObjectUtil.isEmpty(req.getList())) {
      AtomicInteger index = new AtomicInteger(1);
      req.setPage(index.getAndIncrement());
      req.setSize(DEF_BATCH_PAGE_SIZE);
      QuickSearchVO page = quickSearch(req);
      List<DocumentEs> list = page.getList();
      res.addAll(list);
      while (true) {
        req.setPage(index.getAndIncrement());
        QuickSearchVO vo = quickSearch(req);
        if (ObjectUtil.isEmpty(vo.getList())) {
          break;
        }
        List<DocumentEs> voList = vo.getList();
        res.addAll(voList);
      }
      return list;
    } else {
      // 选择条目导出
      List<DocumentEs> selectedList = req.getList();
      // 判断是否符合规则
      Map<List<Object>, List<Long>> map =
          selectedList.stream()
              .collect(
                  Collectors.groupingBy(
                      SearchEsSvc::buildKey,
                      Collectors.mapping(DocumentEs::getId, Collectors.toList())));
      map.forEach(
          (key, ids) -> {
            List<DocumentEs> list = documentEsSvc.listByIds(ids, key.get(1).toString());
            res.addAll(list);
          });
    }
    return res;
  }

  @Override
  public List<String> quickSearchExport(QuickSearchExportDTO req) {
    List<DocumentEs> list = quickSearchData(req);
    return writeArchDataToExcel(list);
  }

  @Override
  public List<? extends SystemBO> advancedSearchData(AdvancedSearchExportDTO req) {
    List<SystemBO> res = Lists.newArrayList();
    // 全部导出
    if (ObjectUtil.isEmpty(req.getList())) {
      AtomicInteger index = new AtomicInteger(1);
      req.setPage(index.getAndIncrement());
      req.setSize(DEF_BATCH_PAGE_SIZE);
      AdvancedSearchVO page = advancedSearch(req);
      List<DocumentEs> list = page.getList();
      res.addAll(list);
      while (true) {
        req.setPage(index.getAndIncrement());
        AdvancedSearchVO vo = advancedSearch(req);
        if (ObjectUtil.isEmpty(vo.getList())) {
          break;
        }
        List<DocumentEs> voList = vo.getList();
        res.addAll(voList);
      }
      return list;
    } else {
      // 选择条目导出
      List<DocumentEs> selectedList = req.getList();
      // 判断是否符合规则
      Map<List<Object>, List<Long>> map =
          selectedList.stream()
              .collect(
                  Collectors.groupingBy(
                      SearchEsSvc::buildKey,
                      Collectors.mapping(DocumentEs::getId, Collectors.toList())));
      map.forEach(
          (key, ids) -> {
            String tableId = key.get(1).toString();
            if (ObjectUtil.contains(tableId, ArchTmplBO.DOCUMENT)) {
              List<DocumentEs> list = documentEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
            if (ObjectUtil.contains(tableId, ArchTmplBO.VOLUME)) {
              List<VolumeEs> list = volumeEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
          });
    }
    return res;
  }

  @Override
  public List<String> advancedSearchExport(AdvancedSearchExportDTO req) {
    List<? extends SystemBO> list = advancedSearchData(req);
    return writeArchDataToExcel(list);
  }

  @Override
  public List<? extends SystemBO> cardSearchData(CardSearchExportDTO req) {
    List<SystemBO> res = Lists.newArrayList();
    // 全部导出
    if (ObjectUtil.isEmpty(req.getList())) {
      AtomicInteger index = new AtomicInteger(1);
      req.setPage(index.getAndIncrement());
      req.setSize(DEF_BATCH_PAGE_SIZE);
      CardSearchVO page = cardSearch(req);
      List<DocumentEs> list = page.getList();
      res.addAll(list);
      while (true) {
        req.setPage(index.getAndIncrement());
        CardSearchVO vo = cardSearch(req);
        if (ObjectUtil.isEmpty(vo.getList())) {
          break;
        }
        List<DocumentEs> voList = vo.getList();
        res.addAll(voList);
      }
      return list;
    } else {
      // 选择条目导出
      List<DocumentEs> selectedList = req.getList();
      // 判断是否符合规则
      Map<List<Object>, List<Long>> map =
          selectedList.stream()
              .collect(
                  Collectors.groupingBy(
                      SearchEsSvc::buildKey,
                      Collectors.mapping(DocumentEs::getId, Collectors.toList())));
      map.forEach(
          (key, ids) -> {
            String tableId = key.get(1).toString();
            if (ObjectUtil.contains(tableId, ArchTmplBO.DOCUMENT)) {
              List<DocumentEs> list = documentEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
            if (ObjectUtil.contains(tableId, ArchTmplBO.VOLUME)) {
              List<VolumeEs> list = volumeEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
          });
    }
    return res;
  }

  @Override
  public List<String> cardSearchExport(CardSearchExportDTO req) {
    List<? extends SystemBO> list = cardSearchData(req);
    return writeArchDataToExcel(list);
  }

  @Override
  public List<? extends SystemBO> catalogSearchData(CatalogSearchExportDTO req) {
    List<SystemBO> res = Lists.newArrayList();
    // 全部导出
    if (ObjectUtil.isEmpty(req.getList())) {
      AtomicInteger index = new AtomicInteger(1);
      req.setPage(index.getAndIncrement());
      req.setSize(DEF_BATCH_PAGE_SIZE);
      CatalogSearchVO page = catalogSearch(req);
      List<DocumentEs> list = page.getList();
      res.addAll(list);
      while (true) {
        req.setPage(index.getAndIncrement());
        CatalogSearchVO vo = catalogSearch(req);
        if (ObjectUtil.isEmpty(vo.getList())) {
          break;
        }
        List<DocumentEs> voList = vo.getList();
        res.addAll(voList);
      }
      return list;
    } else {
      // 选择条目导出
      List<DocumentEs> selectedList = req.getList();
      // 判断是否符合规则
      Map<List<Object>, List<Long>> map =
          selectedList.stream()
              .collect(
                  Collectors.groupingBy(
                      SearchEsSvc::buildKey,
                      Collectors.mapping(DocumentEs::getId, Collectors.toList())));
      map.forEach(
          (key, ids) -> {
            String tableId = key.get(1).toString();
            if (ObjectUtil.contains(tableId, ArchTmplBO.DOCUMENT)) {
              List<DocumentEs> list = documentEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
            if (ObjectUtil.contains(tableId, ArchTmplBO.VOLUME)) {
              List<VolumeEs> list = volumeEsSvc.listByIds(ids, key.get(1).toString());
              res.addAll(list);
            }
          });
    }
    return res;
  }

  @Override
  public List<String> catalogSearchExport(CatalogSearchExportDTO req) {
    List<? extends SystemBO> list = catalogSearchData(req);
    return writeArchDataToExcel(list);
  }

  @NotNull
  private List<String> writeArchDataToExcel(List<? extends SystemBO> list) {
    Map<ImmutablePair<Long, String>, ? extends List<? extends SystemBO>> map =
        list.stream()
            .collect(
                Collectors.groupingBy(s -> new ImmutablePair<>(s.getArchTypeId(), s.getTableId())));
    return map.entrySet().stream()
        .map(
            arch -> {
              return writeListToExcelFile(
                  arch.getKey().getLeft(),
                  arch.getKey().getRight(),
                  ConvertUtil.toList(arch.getValue()));
            })
        .collect(Collectors.toList());
  }

  @Override
  public List<SearchViewVO> list(SearchViewListQueryDTO req) {
    if (ObjectUtil.isEmpty(req.getList())) {
      return Lists.newArrayList();
    }
    Map<String, List<Long>> map =
        req.getList().stream()
            .collect(
                Collectors.groupingBy(
                    SearchViewDTO::getTableId,
                    Collectors.mapping(SearchViewDTO::getId, Collectors.toList())));
    return map.entrySet().stream()
        .map(
            entry -> {
              if (ObjectUtil.contains(entry.getKey(), ArchTmplBO.VOLUME)) {
                List<VolumeEs> volumeEs = volumeEsSvc.listByIds(entry.getValue(), entry.getKey());
                if (ObjectUtil.isEmpty(volumeEs)) {
                  return Lists.<SearchViewVO>newArrayList();
                }
                return volumeEs.stream()
                    .map(
                        doc -> {
                          SearchViewVO<VolumeEs> vo = new SearchViewVO<>();
                          vo.setData(doc);
                          vo.setAuth(authCmd.getAuth(doc.getArchTypeId(), doc));
                          return vo;
                        })
                    .collect(Collectors.toList());
              }
              // 查询文书档案
              List<DocumentEs> documentEs =
                  documentEsSvc.listByIds(entry.getValue(), entry.getKey());
              if (ObjectUtil.isEmpty(documentEs)) {
                return Lists.<SearchViewVO>newArrayList();
              }
              return documentEs.stream()
                  .map(
                      doc -> {
                        SearchViewVO<DocumentEs> vo = new SearchViewVO<>();
                        vo.setData(doc);
                        vo.setAuth(authCmd.getAuth(doc.getArchTypeId(), doc));
                        return vo;
                      })
                  .collect(Collectors.toList());
            })
        .flatMap(Collection::stream)
        .collect(Collectors.toList());
  }

  @Override
  public List<String> exportList(SearchViewListQueryDTO req) {
    if (ObjectUtil.isEmpty(req.getList())) {
      throw BizTipException.instance(ErrorCode.VALIDATE_NOT_NULL, "所选数据不能为空");
    }

    Map<ImmutablePair<Long, String>, List<Long>> map =
        req.getList().stream()
            .collect(
                Collectors.groupingBy(
                    s -> new ImmutablePair<Long, String>(s.getArchTypeId(), s.getTableId()),
                    Collectors.mapping(SearchViewDTO::getId, Collectors.toList())));
    List<String> pathList =
        map.entrySet().stream()
            .map(
                arch -> {
                  if (ObjectUtil.contains(arch.getKey().getRight(), ArchTmplBO.VOLUME)) {
                    List<VolumeEs> volumeEs =
                        volumeEsSvc.listByIds(arch.getValue(), arch.getKey().getRight());
                    return writeListToExcelFile(
                        arch.getKey().getLeft(),
                        arch.getKey().getRight(),
                        ConvertUtil.toList(volumeEs));
                  } else {
                    List<DocumentEs> documentEs =
                        documentEsSvc.listByIds(arch.getValue(), arch.getKey().getRight());
                    return writeListToExcelFile(
                        arch.getKey().getLeft(),
                        arch.getKey().getRight(),
                        ConvertUtil.toList(documentEs));
                  }
                })
            .collect(Collectors.toList());
    if (ObjectUtil.isEmpty(pathList)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "未找到导出的数据信息");
    }
    return pathList;
  }

  @NotNull
  private String writeListToExcelFile(
      Long archTypeId, String tableId, List<Map<String, Object>> maps) {
    List<ArchiveFieldListVO> fieldListVOS = archiveFieldSvc.selectListByTableId(tableId);
    List<String> header =
        fieldListVOS.stream()
            .map(ArchiveFieldListVO::getDbFieldName)
            .filter(ObjectUtil::isNotEmpty)
            .collect(Collectors.toList());
    List<String> keys =
        fieldListVOS.stream()
            .map(ArchiveFieldListVO::getDbFieldKey)
            .filter(ObjectUtil::isNotEmpty)
            .collect(Collectors.toList());
    String tempPath = ossTemplate.getTempPath();
    ArchiveTypeVO archiveTypeVO =
        Optional.ofNullable(archiveTypeSvc.view(archTypeId)).orElse(new ArchiveTypeVO());
    String typeName = ObjectUtil.contains(tableId, ArchTmplBO.VOLUME) ? "案卷" : "文件";
    String path =
        tempPath + archiveTypeVO.getName() + "_" + archiveTypeVO.getId() + typeName + ".xlsx";
    EasyExcel.write(path)
        // 这里放入动态头
        .head(ExcelUtil.head(header))
        .sheet("数据")
        .doWrite(ExcelUtil.detail(maps, keys));
    return path;
  }

  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  class ServiceInfo<T> {
    private IServiceEs<T> serviceEs;
    private Class<T> clazz;
  }
}
